const std = @import("std");
const awtk = @import("awtk");
const object = @import("object.zig");

const c = awtk.c;

pub fn isModelList(Type: type) bool {
    return std.mem.indexOf(u8, @typeName(Type), "model_list.ModelList(") != null;
}

const ItemInfo = struct {
    idx: usize,
    name: []const u8,
};

fn parseItemInfo(path: []const u8) ?ItemInfo {
    const start = std.mem.indexOfScalar(u8, path, '[') orelse return null;
    const end = std.mem.indexOfScalar(u8, path, ']') orelse return null;
    const idx_str = path[start + 1 .. end];
    const idx = std.fmt.parseUnsigned(usize, idx_str, 10) catch return null;
    return ItemInfo{
        .idx = idx,
        .name = path[end + 2 ..],
    };
}

const Size = struct {
    @"#size": u32 = 0,

    pub const Object = object.Object(Size, .{});

    pub fn set(obj: *awtk.Object, size: usize) !void {
        const size_obj = Object.cast(obj) orelse return object.Error.Mismatch;
        const s: u32 = @intCast(size);
        if (size_obj.model.@"#size" != s) {
            size_obj.model.@"#size" = s;
            awtk.notify(obj, c.EVT_ITEMS_CHANGED);
        }
    }
};

pub fn ModelList(Model: type) type {
    return struct {
        models: ModelArray = undefined,
        notifier: *awtk.Object = undefined, // 为Size.Object的实例，用于响应getProp #size

        const Self = @This();
        const ModelArray = std.ArrayList(Model);

        pub fn init(self: *Self) !void {
            self.models = try ModelArray.initCapacity(awtk.gallocator, 8);
            self.notifier = try Size.Object.create();
        }

        pub fn deinit(self: *Self) void {
            _ = c.tk_object_unref(self.notifier);
            for (self.models.items) |*model| {
                model.deinit();
            }

            self.models.deinit(awtk.gallocator);
        }

        pub fn count(self: Self) usize {
            return self.models.items.len;
        }

        pub fn emitter(self: *Self) *awtk.Emitter {
            return &self.notifier.emitter;
        }

        pub fn append(self: *Self, model: *Model) !void {
            errdefer model.deinit();
            try self.models.append(awtk.gallocator, model.*);
            Size.set(self.notifier, self.models.items.len) catch |e| {
                std.log.err("{} fail to set notifier: {}", .{ Self, e });
            };
        }

        pub fn remove(self: *Self, idx: usize) !void {
            if (idx >= self.models.items.len) {
                return error.OutOfBound;
            }

            var model = self.models.orderedRemove(idx);
            model.deinit();
            try Size.set(self.notifier, self.models.items.len);
        }

        pub fn getProp(self: Self, path: []const u8, v: *awtk.Value) c.ret_t {
            //std.log.debug("{}.{s}: {s}", .{ @This(), @src().fn_name, path });
            var ret: c.ret_t = c.RET_NOT_FOUND;

            const info = parseItemInfo(path);
            if (info) |i| {
                if (i.idx < self.models.items.len) {
                    ret = object.getFieldProp(i.name, Model, self.models.items[i.idx], v);
                }
            } else {
                _ = c.value_set_object(v, self.notifier); // 响应 #size
                ret = c.RET_OK;
            }

            return ret;
        }

        pub fn setProp(self: *Self, path: []const u8, v: *const awtk.Value) c.ret_t {
            //std.log.debug("{}.{s}: {s}", .{ @This(), @src().fn_name, path });
            var ret: c.ret_t = c.RET_NOT_FOUND;

            const info = parseItemInfo(path);
            if (info) |i| {
                if (i.idx < self.models.items.len) {
                    ret = object.setFieldProp(i.name, Model, &self.models.items[i.idx], v);
                }
            }

            return ret;
        }

        pub fn item(self: Self, idx: usize) ?*Model {
            return if (idx < self.models.items.len) &self.models.items[idx] else null;
        }

        pub fn notify(self: Self, event_type: u32) void {
            awtk.notify(self.notifier, event_type);
        }

        fn onRequestResult(creq: ?*c.navigator_request_t, cresult: ?*const awtk.Value) callconv(.c) c.ret_t {
            const req = creq orelse return c.RET_BAD_PARAMS;
            const result = cresult orelse return c.RET_BAD_PARAMS;
            const self_ptr = c.tk_object_get_prop_pointer(c.OBJECT(req), awtk.baseTypeName(Self)) orelse return c.RET_BAD_PARAMS;
            const self: *Self = @ptrCast(@alignCast(self_ptr));
            const evt = c.value_uint32(result);

            self.notify(evt);
            return c.RET_OK;
        }

        pub fn navigateWithItem(self: *Self, window_name: [:0]const u8, idx: usize) !void {
            const model = self.item(idx) orelse return error.OutOfBound;

            const req = c.navigator_request_create(window_name, onRequestResult) orelse return error.TkOOM;
            _ = c.tk_object_set_prop_pointer(c.OBJECT(req), awtk.baseTypeName(Model), @ptrCast(model));
            _ = c.tk_object_set_prop_pointer(c.OBJECT(req), awtk.baseTypeName(Self), @ptrCast(self));
            const ret = c.navigator_handle_request(c.navigator(), req);
            _ = c.tk_object_unref(c.OBJECT(req));

            if (ret != c.RET_OK) {
                return error.NavigatorRequestError;
            }
        }
    };
}
